-
Notifications
You must be signed in to change notification settings - Fork 111
fix(browse): Fix issue where files would sometimes never load #365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThis set of changes removes all React Query-based prefetching logic for file sources and folder contents, deletes the associated hooks, and refactors several components from client-side to server-side async components. Data fetching is now performed directly in server components, with error handling and UI loading states adjusted accordingly. Utility parsing logic is consolidated in a new function. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant BrowsePage (server)
participant CodePreviewPanel/TreePreviewPanel (server)
participant DataService
User->>BrowsePage (server): Request page with params
BrowsePage (server)->>CodePreviewPanel/TreePreviewPanel: Render with explicit props
CodePreviewPanel/TreePreviewPanel->>DataService: await getRepoInfoByName / getFolderContents / getFileSource
DataService-->>CodePreviewPanel/TreePreviewPanel: Return data or error
CodePreviewPanel/TreePreviewPanel-->>BrowsePage (server): Rendered JSX
BrowsePage (server)-->>User: Send rendered page
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🔭 Outside diff range comments (2)
packages/web/src/features/fileTree/components/pureFileTreePanel.tsx (1)
54-60
: Avoid mutating React state insidetransformTree
currentNode.isCollapsed = isCollapsed
directly mutates a node that still belongs to the previous state tree.
Although you later spread-copy the node, the mutation happens first, so the original tree is mutated before React receives the new tree, violating React’s immutability contract and risking subtle bugs (e.g. missed re-renders in memoised children).- if (currentNode.path === path) { - currentNode.isCollapsed = isCollapsed; - } - return currentNode; + if (currentNode.path === path) { + /* return a new object – never mutate the existing one */ + return { ...currentNode, isCollapsed }; + } + return currentNode;packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx (1)
2-6
: Remove unused imports from client-side version.These imports are no longer needed after the conversion to a server component:
useBrowseParams
(now receives props)useQuery
(now uses direct await)useDomain
(now receives domain as prop)Loader2
(loading handled by Suspense in parent)-import { useBrowseParams } from "@/app/[domain]/browse/hooks/useBrowseParams"; -import { useQuery } from "@tanstack/react-query"; -import { getFileSource } from "@/features/search/fileSourceApi"; -import { useDomain } from "@/hooks/useDomain"; -import { Loader2 } from "lucide-react"; +import { getFileSource } from "@/features/search/fileSourceApi";
🧹 Nitpick comments (5)
packages/web/src/features/fileTree/components/pureFileTreePanel.tsx (2)
95-98
: Redundantkey
prop duplicationBoth the
React.Fragment
(line 95) and the nestedFileTreeItemComponent
(line 97) receive the samekey={node.path}
.
The extra key on the child component is superfluous and can be safely removed to keep the markup tidy.-<React.Fragment key={node.path}> - <FileTreeItemComponent - key={node.path} +<React.Fragment key={node.path}> + <FileTreeItemComponent
112-113
:renderTree
dependency list may cause unnecessary re-creations
renderTree
depends onscrollAreaRef
(passed to children) but the ref is omitted from the dependency array.
WhileuseRef
objects are stable, future changes (e.g. swapping touseState
or replacing the ref) would silently break memoisation.
For completeness and future-proofing, include it:- }, [path, onNodeClicked]); + }, [path, onNodeClicked, scrollAreaRef]);packages/web/src/app/[domain]/browse/hooks/utils.ts (2)
3-3
: Fix typo in variable name.The variable name
sentinalIndex
should besentinelIndex
(missing 'e').- const sentinalIndex = pathParam.search(/\/-\/(tree|blob)\//); + const sentinelIndex = pathParam.search(/\/-\/(tree|blob)\//);You'll also need to update the usage on lines 4, 8, and 13.
16-16
: Fix typo in comment.The comment mentions
decodedURIComponent
but should bedecodeURIComponent
.- // @note: decodedURIComponent is needed here incase the path contains a space. + // @note: decodeURIComponent is needed here in case the path contains a space.packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx (1)
25-27
: Enhance error handling with more descriptive messagesThe current error handling doesn't provide enough context about which operation failed or why.
if (isServiceError(folderContentsResponse) || isServiceError(repoInfoResponse)) { - return <div>Error loading tree preview</div> + const errorMessage = isServiceError(repoInfoResponse) + ? `Failed to load repository information for ${repoName}` + : `Failed to load folder contents at ${path}`; + return <div className="p-4 text-red-500">{errorMessage}</div> }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx
(2 hunks)packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx
(1 hunks)packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx
(2 hunks)packages/web/src/app/[domain]/browse/[...path]/page.tsx
(1 hunks)packages/web/src/app/[domain]/browse/components/fileSearchCommandDialog.tsx
(0 hunks)packages/web/src/app/[domain]/browse/hooks/useBrowseParams.ts
(1 hunks)packages/web/src/app/[domain]/browse/hooks/utils.ts
(1 hunks)packages/web/src/app/[domain]/components/pathHeader.tsx
(0 hunks)packages/web/src/ee/features/codeNav/components/exploreMenu/referenceList.tsx
(0 hunks)packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx
(0 hunks)packages/web/src/features/fileTree/components/pureFileTreePanel.tsx
(1 hunks)packages/web/src/hooks/usePrefetchFileSource.ts
(0 hunks)packages/web/src/hooks/usePrefetchFolderContents.ts
(0 hunks)
💤 Files with no reviewable changes (6)
- packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx
- packages/web/src/app/[domain]/components/pathHeader.tsx
- packages/web/src/hooks/usePrefetchFolderContents.ts
- packages/web/src/hooks/usePrefetchFileSource.ts
- packages/web/src/app/[domain]/browse/components/fileSearchCommandDialog.tsx
- packages/web/src/ee/features/codeNav/components/exploreMenu/referenceList.tsx
🧰 Additional context used
📓 Path-based instructions (1)
`**/*`: Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.
**/*
: Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.
packages/web/src/features/fileTree/components/pureFileTreePanel.tsx
packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx
packages/web/src/app/[domain]/browse/hooks/utils.ts
packages/web/src/app/[domain]/browse/hooks/useBrowseParams.ts
packages/web/src/app/[domain]/browse/[...path]/page.tsx
packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx
packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: build
- GitHub Check: build
- GitHub Check: build
🔇 Additional comments (7)
packages/web/src/app/[domain]/browse/hooks/utils.ts (1)
2-37
: LGTM - Well-structured utility function.The path parsing logic is well-implemented with proper error handling and clear separation of concerns. This centralizes the parsing logic effectively, supporting the broader refactoring goals.
packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx (1)
1-46
: LGTM - Well-designed pure presentational component.This component provides a clean separation of concerns by accepting data as props while handling UI interactions through hooks. The use of
useCallback
for the click handler is appropriate for performance optimization.packages/web/src/app/[domain]/browse/hooks/useBrowseParams.ts (1)
8-17
: LGTM - Good refactoring to centralize parsing logic.The simplification of this hook by delegating to the utility function is a solid improvement. The memoization ensures the parsing only happens when the pathname changes.
packages/web/src/app/[domain]/browse/[...path]/page.tsx (2)
14-17
: LGTM - Clean server component conversion.The conversion to an async server component with explicit props is well-executed. Using
decodeURIComponent
on the joined path and the utility function for parsing is the correct approach.
20-25
: LGTM - Appropriate loading state handling.The Suspense fallback with loading spinner provides good user feedback during data fetching. This aligns with the PR's goal of moving loading states to the server side.
packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx (1)
21-40
: LGTM - Excellent server component conversion.The conversion from React Query to direct
await
calls is well-implemented. The sequential data fetching, error handling withisServiceError
, and explicit props pattern align perfectly with the PR's objectives to fix the loading issues.packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx (1)
29-46
: Clean separation of concerns!The refactoring to delegate rendering to
PureTreePreviewPanel
while keeping data fetching in the server component is well-structured and aligns with Next.js best practices.
packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx
Outdated
Show resolved
Hide resolved
[edit]: resolved in 417eabf |
f35cf47
to
718a26a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx (2)
26-28
: Enhance error handling with more specific error informationThe current error handling is quite generic. Consider providing more specific error messages or logging for debugging purposes.
- if (isServiceError(folderContentsResponse) || isServiceError(repoInfoResponse)) { - return <div>Error loading tree preview</div> + if (isServiceError(folderContentsResponse) || isServiceError(repoInfoResponse)) { + const folderError = isServiceError(folderContentsResponse) ? folderContentsResponse.error : null; + const repoError = isServiceError(repoInfoResponse) ? repoInfoResponse.error : null; + console.error('TreePreviewPanel error:', { folderError, repoError }); + return <div>Error loading tree preview. Please try again.</div>
16-47
: Consider user experience implications of server-side renderingWhile the server component refactor fixes the infinite loading issue, users will now see a blank state until the entire component loads. This trade-off is acceptable given the fix, but consider implementing loading UI at a higher level in the component hierarchy.
For future improvements, you might want to:
- Add loading indicators at the page level
- Consider implementing streaming or progressive loading for large folder contents
- Explore Next.js's built-in prefetching mechanisms as mentioned in the PR description
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
CHANGELOG.md
(1 hunks)packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx
(1 hunks)packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx
(1 hunks)packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx
(2 hunks)packages/web/src/app/[domain]/browse/[...path]/page.tsx
(1 hunks)packages/web/src/app/[domain]/browse/components/fileSearchCommandDialog.tsx
(0 hunks)packages/web/src/app/[domain]/browse/hooks/useBrowseParams.ts
(1 hunks)packages/web/src/app/[domain]/browse/hooks/utils.ts
(1 hunks)packages/web/src/app/[domain]/components/pathHeader.tsx
(0 hunks)packages/web/src/ee/features/codeNav/components/exploreMenu/referenceList.tsx
(0 hunks)packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx
(0 hunks)packages/web/src/features/fileTree/components/pureFileTreePanel.tsx
(1 hunks)packages/web/src/hooks/usePrefetchFileSource.ts
(0 hunks)packages/web/src/hooks/usePrefetchFolderContents.ts
(0 hunks)
💤 Files with no reviewable changes (6)
- packages/web/src/app/[domain]/components/pathHeader.tsx
- packages/web/src/hooks/usePrefetchFolderContents.ts
- packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx
- packages/web/src/hooks/usePrefetchFileSource.ts
- packages/web/src/ee/features/codeNav/components/exploreMenu/referenceList.tsx
- packages/web/src/app/[domain]/browse/components/fileSearchCommandDialog.tsx
✅ Files skipped from review due to trivial changes (2)
- CHANGELOG.md
- packages/web/src/features/fileTree/components/pureFileTreePanel.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
- packages/web/src/app/[domain]/browse/hooks/useBrowseParams.ts
- packages/web/src/app/[domain]/browse/hooks/utils.ts
- packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx
- packages/web/src/app/[domain]/browse/[...path]/page.tsx
- packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (4)
packages/web/src/app/[domain]/browse/[...path]/components/treePreviewPanel.tsx (4)
5-7
: LGTM: Clean imports for server componentThe imports are appropriate for the server component refactor, bringing in the necessary server actions and utility functions.
9-14
: LGTM: Well-defined interface with explicit propsThe interface properly defines the required props for the server component, making data dependencies explicit and improving type safety.
17-24
: LGTM: Parallel data fetching implementationThe use of
Promise.all
for concurrent data fetching is excellent for performance and addresses the previous review comment about parallel requests.
45-45
: LGTM: Clean separation of concernsThe delegation to
PureTreePreviewPanel
is a good architectural decision that separates data fetching from rendering logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
packages/web/src/app/[domain]/browse/hooks/utils.test.ts (3)
48-49
: Remove unnecessary empty lines.There are extra empty lines that should be cleaned up for better code consistency.
describe('blob paths', () => { - - it('should parse blob path with file', () => {
158-168
: Make error assertions more specific.The error tests use generic
.toThrow()
without verifying the specific error messages. Consider testing the exact error messages to ensure the function provides meaningful feedback.it('should throw error for blob path with trailing slash and no path', () => { expect(() => { getBrowseParamsFromPathParam('github.com/sourcebot-dev/zoekt@HEAD/-/blob/'); - }).toThrow(); + }).toThrow('Invalid browse pathname: "github.com/sourcebot-dev/zoekt@HEAD/-/blob/" - expected to contain a path for blob type'); }); it('should throw error for blob path without trailing slash and no path', () => { expect(() => { getBrowseParamsFromPathParam('github.com/sourcebot-dev/zoekt@HEAD/-/blob'); - }).toThrow(); + }).toThrow('Invalid browse pathname: "github.com/sourcebot-dev/zoekt@HEAD/-/blob" - expected to contain a path for blob type'); });
170-186
: Make remaining error assertions more specific.For consistency and better test reliability, these error tests should also verify specific error messages.
it('should throw error for invalid pattern - missing /-/', () => { expect(() => { getBrowseParamsFromPathParam('github.com/sourcebot-dev/zoekt@HEAD/tree/'); - }).toThrow(); + }).toThrow('Invalid browse pathname: "github.com/sourcebot-dev/zoekt@HEAD/tree/" - expected to contain "/-/(tree|blob)/" pattern'); }); it('should throw error for invalid pattern - missing tree/blob', () => { expect(() => { getBrowseParamsFromPathParam('github.com/sourcebot-dev/zoekt@HEAD/-/invalid/'); - }).toThrow(); + }).toThrow('Invalid browse pathname: "github.com/sourcebot-dev/zoekt@HEAD/-/invalid/" - expected to contain "/-/(tree|blob)/" pattern'); }); it('should throw error for completely invalid format', () => { expect(() => { getBrowseParamsFromPathParam('invalid-path'); - }).toThrow(); + }).toThrow('Invalid browse pathname: "invalid-path" - expected to contain "/-/(tree|blob)/" pattern'); }); it('should throw error for empty string', () => { expect(() => { getBrowseParamsFromPathParam(''); - }).toThrow(); + }).toThrow('Invalid browse pathname: "" - expected to contain "/-/(tree|blob)/" pattern'); });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/web/src/app/[domain]/browse/hooks/utils.test.ts
(1 hunks)packages/web/src/app/[domain]/browse/hooks/utils.ts
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- packages/web/src/app/[domain]/browse/hooks/utils.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/web/src/app/[domain]/browse/hooks/utils.test.ts (1)
packages/web/src/app/[domain]/browse/hooks/utils.ts (1)
getBrowseParamsFromPathParam
(2-43)
🔇 Additional comments (2)
packages/web/src/app/[domain]/browse/hooks/utils.test.ts (2)
1-3
: LGTM! Clean test imports and setup.The imports are appropriate and the test structure follows standard vitest conventions.
4-194
: Excellent test coverage overall.The test suite comprehensively covers all major functionality of the
getBrowseParamsFromPathParam
function:✅ Tree and blob path parsing
✅ Optional revision handling
✅ URL decoding of special characters
✅ Different revision formats (branch, commit, tag)
✅ Edge cases with multiple @ symbols
✅ Error handling for invalid inputsThe tests properly validate the parsing logic and ensure the function correctly extracts repo names, revision names, paths, and path types from complex URL patterns. This thorough coverage supports the refactor from client-side to server-side data fetching mentioned in the PR objectives.
Problem
We noticed that sometimes files would be stuck in a infinite loading state like the following:
This would happen randomly. Usually refreshing the page would fix it.
Solution
This PR moves the file source fetch from the client to the server. The downside of this approach is that all of the client side prefetching we were doing (with
usePrefetchFolderContents.ts
andusePrefetchFileSource.ts
) no longer works, so now users will always see the loading spinner.We should probably switch to links anyways (see #353), so we can probably rely on Next's built-in prefetching with the Link component..
Why was this happening in the first place?
TLDR: I'm not totally sure, but I think it's because we are using server actions incorrectly. See this issue.
I did some digging, and came across this issue that mentioned that server actions are not intended for fetching data and that you can get some unexpected behaviours when you do. This looks to be the case according to the react docs. It seems like this is one of those cases, but I'm not exactly sure why. I confirmed that this was the issue by switching over to using a
fetch
call and was unable to repro. Moving the data fetching to the server also fixed the issue.As a longer-term thing, it might make sense to move away from using server actions (either altogether or for specifically data fetching) to avoid issues like this. Ideally something with as good of a DX (or better). Some options I've come across:
Summary by CodeRabbit
New Features
Refactor
Bug Fixes
Chores